/**
 * 
 */
package net.sf.cldsql.dj.mvc;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.cldsql.dj.container.DiContainer;
import net.sf.cldsql.dj.container.base.DiAop;
import net.sf.cldsql.dj.container.base.DiContext;
import net.sf.cldsql.dj.db.ConnPool;
import net.sf.cldsql.dj.utl.DiException;

/**
 * ConnTransAop
 */
public class ConnTransAop implements DiAop<DiService> {
    
    private final String CONTEXT_MAP_BEAN = ConnTransAop.class.getCanonicalName() + "-ContextMap";
    
    final Logger log = LoggerFactory.getLogger(ConnTransAop.class);
    
    private DiContainer container;

    private boolean initConn;

    private boolean initTx;
    
    public void setContainer (DiContainer container) {
        this.container = container;
    }
    
    public class ConnTransAopContext {
        private int connUsageCount = 0;
        private Connection conn;
        public Connection getConn() {
            return conn;
        }
        public void setConn(Connection conn) {
            this.conn = conn;
        }
        public int getConnUsageCount() {
            return connUsageCount;
        }
        public void setConnUsageCount(int connUsageCount) {
            this.connUsageCount = connUsageCount;
        }
        public int increaseConnUsageCount() {
            return ++connUsageCount;
        }
        public int decreaseConnUsageCount() {
            return --connUsageCount;
        }
    }
    
    protected ConnTransAopContext getContext(DiContext execContext) {
        ConnTransAopContext ctx = (ConnTransAopContext)execContext.get(CONTEXT_MAP_BEAN);
        if (ctx == null) {
            ctx = new ConnTransAopContext();
            execContext.put(CONTEXT_MAP_BEAN, ctx);
        }
        return ctx;
    }
    
    public void before(DiContext diContext, DiService delegate, Method method, Object[] args) {
        log.debug("Before weaving of {}.", delegate);
        
        log.debug("Set container {} to {}.", container, delegate);
        delegate.setContainer(container);
        
        ConnTransAopContext ctx = getContext(diContext);
        Connection conn;
        
        // not necessary synchronized for delegate and context
        if (ctx.getConn() == null) {
            log.debug("Initialize conn of {} .", delegate);
            conn = container.get(ConnPool.class).get();
            ctx.setConn(conn);
            this.initConn = true;
            this.initTx = true;
        } else {
            conn = getContext(diContext).getConn();
            log.debug("Set conn {} to {}.", conn, delegate);
            this.initConn = false;
            this.initTx = false;
        }
        delegate.setConn(conn);
    }
    
    public void after(DiContext diContext, DiService delegate, Method method, Object[] args) {
        log.debug("After weaving of {}.", delegate);
        
        try {
            Connection conn = getContext(diContext).getConn();
            log.debug("Commit conn {} of {} .", conn, delegate);
            if (this.initTx) {
                conn.commit();
            }
        } catch (SQLException e) {
            throw new DiException(e);
        } finally {
            releaseConn(diContext, delegate, method);
        }
    }
    
    private void releaseConn(DiContext diContext, DiService delegate, Method method) {
        ConnTransAopContext ctx = getContext(diContext);
        if (this.initConn) {
            Connection conn = ctx.getConn();
            if (conn == null) {
                throw new DiException("Get null connection from context when try to release it.");
            }
            log.debug("Release conn {} of {} .", conn, delegate);
            container.get(ConnPool.class).release(conn);
            ctx.setConn(null);
        }
    }
    
    public void error(DiContext diContext, DiService delegate, Method method, Object[] args, Exception ex) {
        log.debug("Error weaving of {} for {}.", delegate, ex);
        
        Connection conn = getContext(diContext).getConn();
        if (this.initTx) {
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException e) {
                    log.error("Error occured when rollback conn : ", e);
                } finally {
                    log.debug("Release conn {} of {} for {}.", conn, delegate, ex);
                    releaseConn(diContext, delegate, method);
                }
            } else {
                throw new DiException("Get null connection from context when try to rollback it.", ex);
            }
        }
        throw new DiException(ex);
    }
}
